QUnit.module('DoublyLinkedList', () =>
{
    QUnit.test('should create empty linked list', (assert) =>
    {
        const linkedList = new ds.DoublyLinkedList();
        assert.deepEqual(linkedList.toString(), '');
    });

    QUnit.test('should append node to linked list', (assert) =>
    {
        const linkedList = new ds.DoublyLinkedList();

        assert.deepEqual(linkedList.head, null);
        assert.deepEqual(linkedList.tail, null);

        linkedList.append(1);
        linkedList.append(2);

        assert.deepEqual(linkedList.head.next.value, 2);
        assert.deepEqual(linkedList.tail.previous.value, 1);
        assert.deepEqual(linkedList.toString(), '1,2');
    });

    QUnit.test('should prepend node to linked list', (assert) =>
    {
        const linkedList = new ds.DoublyLinkedList();

        linkedList.prepend(2);
        assert.deepEqual(linkedList.head.value, 2);
        assert.deepEqual(linkedList.tail.value, 2);

        linkedList.append(1);
        linkedList.prepend(3);

        assert.deepEqual(linkedList.head.next.next.previous, linkedList.head.next);
        assert.deepEqual(linkedList.tail.previous.next, linkedList.tail);
        assert.deepEqual(linkedList.tail.previous.value, 2);
        assert.deepEqual(linkedList.toString(), '3,2,1');
    });

    QUnit.test('should create linked list from array', (assert) =>
    {
        const linkedList = new ds.DoublyLinkedList();
        linkedList.fromArray([1, 1, 2, 3, 3, 3, 4, 5]);

        assert.deepEqual(linkedList.toString(), '1,1,2,3,3,3,4,5');
    });

    QUnit.test('should delete node by value from linked list', (assert) =>
    {
        const linkedList = new ds.DoublyLinkedList();

        assert.deepEqual(linkedList.deleteAll(5), null);

        linkedList.append(1);
        linkedList.append(1);
        linkedList.append(2);
        linkedList.append(3);
        linkedList.append(3);
        linkedList.append(3);
        linkedList.append(4);
        linkedList.append(5);

        assert.deepEqual(linkedList.head.value, 1);
        assert.deepEqual(linkedList.tail.value, 5);

        const deletedNode = linkedList.deleteAll(3);
        assert.deepEqual(deletedNode.value, 3);
        assert.deepEqual(linkedList.tail.previous.previous.value, 2);
        assert.deepEqual(linkedList.toString(), '1,1,2,4,5');

        linkedList.deleteAll(3);
        assert.deepEqual(linkedList.toString(), '1,1,2,4,5');

        linkedList.deleteAll(1);
        assert.deepEqual(linkedList.toString(), '2,4,5');

        assert.deepEqual(linkedList.head.value, 2);
        assert.deepEqual(linkedList.head.next.next, linkedList.tail);
        assert.deepEqual(linkedList.tail.previous.previous, linkedList.head);
        assert.deepEqual(linkedList.tail.value, 5);

        linkedList.deleteAll(5);
        assert.deepEqual(linkedList.toString(), '2,4');

        assert.deepEqual(linkedList.head.value, 2);
        assert.deepEqual(linkedList.tail.value, 4);

        linkedList.deleteAll(4);
        assert.deepEqual(linkedList.toString(), '2');

        assert.deepEqual(linkedList.head.value, 2);
        assert.deepEqual(linkedList.tail.value, 2);
        assert.deepEqual(linkedList.head, linkedList.tail);

        linkedList.deleteAll(2);
        assert.deepEqual(linkedList.toString(), '');
    });

    QUnit.test('should delete linked list tail', (assert) =>
    {
        const linkedList = new ds.DoublyLinkedList();

        assert.deepEqual(linkedList.deleteTail(), null);

        linkedList.append(1);
        linkedList.append(2);
        linkedList.append(3);

        assert.deepEqual(linkedList.head.value, 1);
        assert.deepEqual(linkedList.tail.value, 3);

        const deletedNode1 = linkedList.deleteTail();

        assert.deepEqual(deletedNode1, 3);
        assert.deepEqual(linkedList.toString(), '1,2');
        assert.deepEqual(linkedList.head.value, 1);
        assert.deepEqual(linkedList.tail.value, 2);

        const deletedNode2 = linkedList.deleteTail();

        assert.deepEqual(deletedNode2, 2);
        assert.deepEqual(linkedList.toString(), '1');
        assert.deepEqual(linkedList.head.value, 1);
        assert.deepEqual(linkedList.tail.value, 1);

        const deletedNode3 = linkedList.deleteTail();

        assert.deepEqual(deletedNode3, 1);
        assert.deepEqual(linkedList.toString(), '');
        assert.deepEqual(linkedList.head, null);
        assert.deepEqual(linkedList.tail, null);
    });

    QUnit.test('should delete linked list head', (assert) =>
    {
        const linkedList = new ds.DoublyLinkedList();

        assert.deepEqual(linkedList.deleteHead(), null);

        linkedList.append(1);
        linkedList.append(2);

        assert.deepEqual(linkedList.head.value, 1);
        assert.deepEqual(linkedList.tail.value, 2);

        const deletedNode1 = linkedList.deleteHead();

        assert.deepEqual(deletedNode1, 1);
        assert.deepEqual(linkedList.head.previous, null);
        assert.deepEqual(linkedList.toString(), '2');
        assert.deepEqual(linkedList.head.value, 2);
        assert.deepEqual(linkedList.tail.value, 2);

        const deletedNode2 = linkedList.deleteHead();

        assert.deepEqual(deletedNode2, 2);
        assert.deepEqual(linkedList.toString(), '');
        assert.deepEqual(linkedList.head, null);
        assert.deepEqual(linkedList.tail, null);
    });

    QUnit.test('should be possible to store objects in the list and to print them out', (assert) =>
    {
        const linkedList = new ds.DoublyLinkedList();

        const nodeValue1 = { value: 1, key: 'key1' };
        const nodeValue2 = { value: 2, key: 'key2' };

        linkedList
            .append(nodeValue1)
            .prepend(nodeValue2);

        const nodeStringifier = value => `${value.key}:${value.value}`;

        assert.deepEqual(linkedList.toString(nodeStringifier), 'key2:2,key1:1');
    });

    QUnit.test('should find node by value', (assert) =>
    {
        const linkedList = new ds.DoublyLinkedList<number>();

        assert.deepEqual(linkedList.find(5), null);

        linkedList.append(1);
        assert.deepEqual(linkedList.find(1) != null, true);

        linkedList
            .append(2)
            .append(3);

        const node = linkedList.find(2);

        assert.deepEqual(node.value, 2);
        assert.deepEqual(linkedList.find(5), null);
    });

    QUnit.test('should find node by callback', (assert) =>
    {
        const linkedList = new ds.DoublyLinkedList<{ value: number, key: string }>();

        linkedList
            .append({ value: 1, key: 'test1' })
            .append({ value: 2, key: 'test2' })
            .append({ value: 3, key: 'test3' });

        const node = linkedList.findByFunc(value => value.key === 'test2');

        assert.deepEqual(node != null, true);
        assert.deepEqual(node.value.value, 2);
        assert.deepEqual(node.value.key, 'test2');
        assert.deepEqual(linkedList.findByFunc(value => value.key === 'test5'), null);
    });

    QUnit.test('should find node by means of custom compare function', (assert) =>
    {
        const comparatorFunction = (a, b) =>
        {
            if (a.customValue === b.customValue)
            {
                return 0;
            }

            return a.customValue < b.customValue ? -1 : 1;
        };

        const linkedList = new ds.DoublyLinkedList(comparatorFunction);

        linkedList
            .append({ value: 1, customValue: 'test1' })
            .append({ value: 2, customValue: 'test2' })
            .append({ value: 3, customValue: 'test3' });

        const node = linkedList.find({ value: 2, customValue: 'test2' });

        assert.deepEqual(node != null, true);
        assert.deepEqual(node.value.value, 2);
        assert.deepEqual(node.value.customValue, 'test2');
        assert.deepEqual(linkedList.find({ value: 2, customValue: 'test5' }), null);
    });

    QUnit.test('should reverse linked list', (assert) =>
    {
        const linkedList = new ds.DoublyLinkedList();

        // Add test values to linked list.
        linkedList
            .append(1)
            .append(2)
            .append(3)
            .append(4);

        assert.deepEqual(linkedList.toString(), '1,2,3,4');
        assert.deepEqual(linkedList.head.value, 1);
        assert.deepEqual(linkedList.tail.value, 4);

        // Reverse linked list.
        linkedList.reverse();

        assert.deepEqual(linkedList.toString(), '4,3,2,1');

        assert.deepEqual(linkedList.head.previous, null);
        assert.deepEqual(linkedList.head.value, 4);
        assert.deepEqual(linkedList.head.next.value, 3);
        assert.deepEqual(linkedList.head.next.next.value, 2);
        assert.deepEqual(linkedList.head.next.next.next.value, 1);

        assert.deepEqual(linkedList.tail.next, null);
        assert.deepEqual(linkedList.tail.value, 1);
        assert.deepEqual(linkedList.tail.previous.value, 2);
        assert.deepEqual(linkedList.tail.previous.previous.value, 3);
        assert.deepEqual(linkedList.tail.previous.previous.previous.value, 4);

        // Reverse linked list back to initial state.
        linkedList.reverse();

        assert.deepEqual(linkedList.toString(), '1,2,3,4');

        assert.deepEqual(linkedList.head.previous, null);
        assert.deepEqual(linkedList.head.value, 1);
        assert.deepEqual(linkedList.head.next.value, 2);
        assert.deepEqual(linkedList.head.next.next.value, 3);
        assert.deepEqual(linkedList.head.next.next.next.value, 4);

        assert.deepEqual(linkedList.tail.next, null);
        assert.deepEqual(linkedList.tail.value, 4);
        assert.deepEqual(linkedList.tail.previous.value, 3);
        assert.deepEqual(linkedList.tail.previous.previous.value, 2);
        assert.deepEqual(linkedList.tail.previous.previous.previous.value, 1);
    });
});
